package discovery;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Random;

import node.NodeDescriptor;
/**
 *Questa classe contiene i metodi reali utilizzati dal discovery per gestire i vari nodi
 * @author ermanno09
 *
 */

public class rDiscovery implements Discovery {
	
	
	/**
	* @param Hashtable <Long,NodeDescriptor> universe questo attributo mantiene tutti i Node Nescriptor all' interno di una Hashtable avente come chiave 
	* l'id del nodo. 
	*/	
	private static Hashtable <Long,NodeDescriptor> universe = null;
	private static Hashtable <Long,Long[]> performance=null;
	

	/**
	* Costruttore che istanzia la Hashtable dei Node Descriptor
	*/
	public rDiscovery(){
	   universe = new Hashtable <Long,NodeDescriptor> ();
	   performance = new Hashtable<Long,Long[]>();
    }
    
	
	/**
	* Questo metodo viene utilizzato per aggiungere i vari node descriptor nella Hashtable universe
	*  @param d il node descriptor del nodo che si vuole aggiornare
	*  @return int ritorna 0 se il delete va a buon fine o 1 se si ha un' errore
	*/	
	@Override
	public int addNode(NodeDescriptor d) {
		// TODO Auto-generated method stub
		try{
			if(!universe.containsKey(d.getId()) &&  !performance.containsKey(d.getId()) ){
				universe.put(d.getId(), d);	
				performance.put(d.getId(), evaluePerformance(d));
				System.out.println("Descriptor :"+ d.getId()+" inserito nella HashTable con  performance HW:"+evaluePerformance(d)[0]+"   SW :"+evaluePerformance(d)[1]);
				return 0;
			}else{
				System.out.println("Impossibile inserire Descriptor: "+d.getId()+".Già presente nell'Hashtable ");								 			
				return 1;
				}
			}
		catch(Exception e){
			System.err.println(e);
			return -1;
		}
				
	}

	
	/**
	* Questo metodo � utilizzato per aggiornare i Node Descriptor dei nodi registrati nel Discovery
	*  @param d il Node Descriptor del nodo che deve essere aggiornato
	*  @return int ritorna 0 se l'aggiornamento  � andato a buon fine o 1 se c'� stato un' errore
	*/			
	@Override
	public int updateNode(NodeDescriptor d) {
		//NodeDescriptor nd=universe.get(d.getId());
		// TODO Auto-generated method stub
		try{
			if(!universe.isEmpty() && universe.containsKey(d.getId()) &&  !performance.containsKey(d.getId())){	
				universe.put(d.getId(), d);
				performance.put(d.getId(), evaluePerformance(d));
				System.out.println("Descriptor "+d.getId()+" aggiornato  performance HW:"+evaluePerformance(d)[0]+"   SW :"+evaluePerformance(d)[1]);				
				return 0;
			}else{
				System.out.println("Impossibile aggiornare il Descriptor: Non presente o Hashtable vuota");		
				return 1;
			}
		}
		 catch(Exception e){
			 System.err.println(e);
			 return -1;
		 }  
	}

	
	/**
	* Questo metodo viene utilizzato per eliminare un nodo dal Discovery
	*  @param d il node descriptor del nodo che deve essere eliminato
	*  @return int ritorna 0 se il delete va a buon fine o 1 se si ha un' errore
	*/		
	@Override
	public int delNode(NodeDescriptor d) {
		// TODO Auto-generated method stub
		try{
			if(!universe.isEmpty()&&universe.containsKey(d.getId()) && !performance.containsKey(d.getId())){
				universe.remove(d);	
				performance.remove(d.getId());
				System.out.println("Descriptor "+d.getId()+" eliminato");
				return 0;
			}else{
				System.out.println("Impossibile eliminare il Descriptor: Non presente o Hashtable vuota");		
				return 1;
			}
		}
		catch(Exception e){
			System.err.println(e);
			return -1;
		}
	}
	/**
	* Questo metodo viene utilizzato per interrogare il Discovery quando un nodo vuole ricevere informazioni su altri nodi riguardo la sottomissione di task o eventi
	*  @param String parmameters la stringa passata al metodo per richiamare un determinato sevizio
	*  @return NodeDescriptor ritorna il NodeDescriptor scelto dal Discovery in base alla politca stabilita.
	*/	
	
	@Override
	public NodeDescriptor lookup(String parameters) {
		
		return splitParameters(parameters);		
	}
	
	
	/**
	* Questo metodo viene utilizzato per suddividere la stringa che arriva come parametro al lookup in base alla politica stabilita
	*  @param String parmameters la stringa passata al metodo proveniente dal lookup
	*  @return NodeDescriptor ritorna il NodeDescriptor scelto dal Discovery in base alla politca stabilita.
	*/		
	public NodeDescriptor splitParameters(String parameters){
		System.out.println(parameters);
		String[] tokens;
		tokens = parameters.split(",");	
		//System.out.println("Ciao");
		//IP:valore,PORT:valore,FILENAME:valore		
		String[] tmp = null;
		String[] values = new String[tokens.length];
		String[] keys = new String[tokens.length];
		//Recupero il valore di IP,porta e filename
		for(int i = 0; i<tokens.length;i++){			
			tmp = tokens[i].split(":");			
			keys[i] = tmp[0];
			System.out.println(keys[i]);//Chiave = MYID
			values[i] = tmp[1];	
			System.out.println(values[i]); //Valore di MYID
		}
		
		NodeDescriptor descriptor = NodeDescriptor.getInstance();
		for(int k =0; k<keys.length;k++){
			if(keys[k].equalsIgnoreCase("RANDOM"))
				descriptor= removeMyID(values[k-1]);									 
				
			if(keys[k].equalsIgnoreCase("IDNODE")){
				if(!universe.isEmpty()){
						//System.out.println("Value:"+key);
					    //visualizzaHASH();
					  Long l= new Long(Long.parseLong(values[k]));    
						System.out.println("Returned NodeDescriptor: "+universe.get(l));	
						descriptor = universe.get(l);
					    //System.out.println("Returned NodeDescriptor: "+descriptor.toString());
				 }
				
			}
			
			if(keys[k].equalsIgnoreCase("MAXPERF")){
			System.out.println("Sono entrato dentro MAXPERF");
			Long l=	maxPerformance(performance,values[k]);
			descriptor = universe.get(l);
			}
			
			
		}						
		return descriptor;
	}
	
	public NodeDescriptor removeMyID(String ID){
		
		Hashtable <Long,NodeDescriptor> temp = new Hashtable<Long,NodeDescriptor>(rDiscovery.universe);
		NodeDescriptor descriptor = null;
		
		if(temp.containsKey(Long.parseLong(ID))){
			 temp.remove(Long.parseLong(ID));
			 Random generator = new Random();
			 int p = generator.nextInt(temp.keySet().size());
			 long key = (Long) temp.keySet().toArray()[p];
			 descriptor = temp.get(key);
		}		
		return descriptor;
	}
	
	
	public Long[] evaluePerformance(NodeDescriptor descriptor){
		Long[] perf= new Long[2];
		perf[0]=(long)descriptor.getNcpu()*descriptor.getMem()*descriptor.getFreq();
		perf[1]=(long)descriptor.getISize()*descriptor.getESize()*descriptor.getIMaxThreads()*descriptor.getEMaxThreads();
		return perf;
	}
	
	public Long maxPerformance(Hashtable<Long,Long[]> performance,String type){
		long valore[]=new long[2];
		Long tmp[]=new Long[2];
		valore[0]=0;
		valore[1]=0;
		Long key;
		Long keyToReturn = null;
	 if(type.equalsIgnoreCase("HW")){
		Enumeration <Long> e = performance.keys();
	    while(e.hasMoreElements()){
	    	key=e.nextElement();
	    	tmp= performance.get(key);
	   	     if(tmp[0]>valore[0]){
	   	    	 valore[0]=tmp[0];
	   	    	 keyToReturn=key;
	   	     } 
	    	System.out.println("Performance HW"+tmp[0]);
	    }
	 return keyToReturn;
	 }
	 else if(type.equalsIgnoreCase("HW")){
			Enumeration <Long> e = performance.keys();
		    while(e.hasMoreElements()){
		    	key=e.nextElement();
		    	tmp= performance.get(key);
		   	     if(tmp[1]>valore[1]){
		   	    	 valore[1]=tmp[1];
		   	    	 keyToReturn=key;
		   	     } 
		    	System.out.println("Perfomance SW"+tmp[1]);
		    }
		 return keyToReturn;
	}else
	 return null;
}
	public void visualizzaHASH(){
		Enumeration <Long> e =universe.keys();
	    NodeDescriptor descriptor=null;
	    long tmp=0;
	    while(e.hasMoreElements()){
	    	tmp=e.nextElement();
	    	System.out.println(tmp);
	    	descriptor=universe.get(tmp);
	    	System.out.println("ID Descriptor:"+descriptor.getId()+", IP:"+descriptor.getIp());
	    }
	}
}
